package cn.dac.shiro.filter;

import cn.dac.shiro.cache.RedisCacheManager;
import com.alibaba.fastjson.JSONObject;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

public class KickoutSessionControlFilter extends AccessControlFilter{
    private static final Logger logger = LoggerFactory.getLogger(KickoutSessionControlFilter.class);


    private String kickoutUrl = "/kickout"; //踢出后到的地址
    private boolean kickoutAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户
    private int maxSession = 1; //同一个帐号最大会话数 默认1

    private SessionManager sessionManager;
    private Cache<String, Deque<Serializable>> cache;


    public KickoutSessionControlFilter(RedisCacheManager cacheManager, SessionManager sessionManager){
        this.sessionManager = sessionManager;
        //用户session队列缓存
        cache = cacheManager.getCache("shiro-activeSessionCache-kickout");
    }



    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {

        try{

                Subject subject = getSubject(servletRequest, servletResponse);


                logger.debug("###【踢用户队列】isAuthenticated："+subject.isAuthenticated());
                if(!subject.isAuthenticated() && !subject.isRemembered()) {
                    //如果没有登录，直接进行之后的流程
                    return true;
                }

                Session session = subject.getSession();
                String loginName = (String) subject.getPrincipal();
                Serializable sessionId = session.getId();
                logger.debug("###【登录验证通过，踢用户队列】当前用户名:"+loginName);
                // 初始化用户的队列放到缓存里
                Deque<Serializable> deque = cache.get(loginName);
                if(deque == null) {
                    deque = new LinkedList<Serializable>();
                }
                //如果队列里没有此sessionId，且用户没有被踢出；放入队列
                if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
                    deque.push(sessionId);
                    cache.put(loginName, deque);
                }
                logger.debug("###【踢用户队列】当前用户sessionId个数："+deque.size());


                //如果队列里的sessionId数超出最大会话数，开始踢人
                while(deque.size() > maxSession) {
                    Serializable kickoutSessionId = null;
                    if(kickoutAfter) { //如果踢出后者
                        kickoutSessionId = deque.removeFirst();
                    } else { //否则踢出前者
                        kickoutSessionId = deque.removeLast();
                    }
                    logger.debug("###【踢用户队列】应踢出的sessionId："+kickoutSessionId);
                    try {
                        Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
                        if(kickoutSession != null) {
                            //设置会话的kickout属性表示踢出了
                            kickoutSession.setAttribute("kickout", true);
                        }
                    } catch (Exception e) {//ignore exception
                        //e.printStackTrace();
                        logger.error("session已丢失",e);
                    }
                }

                //如果被踢出了，直接退出，重定向到踢出后的地址
                if ((Boolean)session.getAttribute("kickout")!=null&&(Boolean)session.getAttribute("kickout") == true) {
                    //会话被踢出了
                    try {
                        subject.logout();
                    } catch (Exception e) {
                    }
                    if(isAjax((HttpServletRequest)servletRequest)){
                        Map<String, String> resultMap = new HashMap<String, String>();
                        resultMap.put("user_status", "300");
                        resultMap.put("message", "您已经在其他地方登录，请重新登录！");
                        out(servletResponse, resultMap);
                    }else{
                        WebUtils.issueRedirect(servletRequest, servletResponse, kickoutUrl);
                    }

                    return false;
                }
                return true;
        } catch (Exception e) { // ignore
            //重定向到登录界面
            WebUtils.issueRedirect(servletRequest, servletResponse, "/login");
            return false;
        }

    }

    /**
     * 判断ajax请求
     * @param request
     * @return
     */
    boolean isAjax(HttpServletRequest request){
        return  (request.getHeader("X-Requested-With") != null  && "XMLHttpRequest".equals( request.getHeader("X-Requested-With").toString())   ) ;
    }
    private void out(ServletResponse hresponse, Map<String, String> resultMap)
            throws IOException {
        try {
            hresponse.setCharacterEncoding("UTF-8");
            PrintWriter out = hresponse.getWriter();
            out.println(JSONObject.toJSON(resultMap).toString());
            out.flush();
            out.close();
        } catch (Exception e) {
            logger.error( "KickoutSessionFilter.class 输出JSON异常",e);
        }
    }
}
